/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 */

import { cloneDeep } from '@baize/wdk';
import { DurationEvent, MetricLogType, parseJsonFile } from '@ohos/hvigor';
import { HvigorConfigLoader } from '@ohos/hvigor/src/common/util/hvigor-config-loader.js';
import { compareVersion, isEqualApiVersion, ApiVersion, compareApiVersion } from '@ohos/sdkmanager-common';
import { ValidateFunction } from 'ajv';
import * as fs from 'fs-extra';
import * as os from 'os';
import path from 'path';
import * as util from 'util';

import { JavaCommandBuilder } from '../../builder/java-command-builder.js';
import { findTargetProduct } from '../../common/find-target-product.js';
import { projectOhosConfigManager } from '../../common/global/project-ohos-config-manager.js';
import { ModulePathInfoIml } from '../../common/iml/module-path-info-iml.js';
import { CommonConst } from '../../const/common-const.js';
import { VersionConst } from '../../const/version-const.js';
import { ModuleModel } from '../../model/module/module-model.js';
import { ProjectModel } from '../../model/project/project-model.js';
import { ProjectBuildProfile } from '../../options/build/project-build-profile.js';
import { RequiredNamed } from '../../options/options.js';
import { ApiType } from '../../project/data/hap-extra-info.js';
import { SdkInfo } from '../../sdk/sdk-info.js';
import { parseApiVersion } from '../../sdk/sdk-util.js';
import { ModuleTargetData } from '../../tasks/data/hap-task-target-data.js';
import { findDuplicateElement } from '../array-util.js';
import { FileUtil } from '../file-util.js';
import { OhosLogger } from '../log/ohos-logger.js';
import { ProcessUtils } from '../process-utils.js';

import { ValidatorStore } from './validator-store.js';
import ProductBuildOpt = ProjectBuildProfile.ProductBuildOpt;
import SigningConfigBuildOpt = ProjectBuildProfile.SigningConfigBuildOpt;
import { DEFAULT_OH_PACKAGE_JSON_FILE_NAME, HVIGOR, DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME } from '@ohos/hvigor/src/common/const/const';
import { HVIGOR_PROJECT_ROOT_DIR } from '@ohos/hvigor/src/common/const/path-const';

import { AppOpt } from '../../options/build/app-ohos-config.js';
import { BuildOpt } from '../../options/build/build-opt';
import { ModuleJson } from '../../options/configure/module-json-options.js';
import { Dependency, DependencyEnum, DependencyType } from '../../project/dependency/core/dependency-interface';
import { sendVerifyProfileRequest } from '../../tasks/sign/sign-request.js';
import { getJson5Obj } from '../json-util.js';
import { isLocalDependency } from '../npm-utils';

export type VersionCheckRes = {
  suggestedVersion: number;
  errorHandler?: Function;
};

/**
 * 校验的工具类
 *
 * @since 2021/12/29
 */
export class ValidateUtil {
  private static _log: OhosLogger = OhosLogger.getLogger(ValidateUtil.name);
  private static richDeviceTypes = new Set<string>(['phone', 'tablet', 'tv', 'wearable', 'car']);
  private static liteDeviceTypes = new Set<string>(['lite Wearable', 'smart Vision', 'router']);
  private static hosApiCompatible = new Map<number, number>([
    [9, 9],
    [10, 9],
    [11, 9],
    [12, 10],
  ]);
  private static FILE_START_EXP = /^file:(.*)/i;

  public static modelVersionCheck() {
    const modelVersion = HvigorConfigLoader.getInstance().getHvigorConfig()?.modelVersion;
    const ohPackagePath = path.resolve(HVIGOR_PROJECT_ROOT_DIR, DEFAULT_OH_PACKAGE_JSON_FILE_NAME);
    const hvigorConfigPath = path.resolve(HVIGOR_PROJECT_ROOT_DIR, HVIGOR , DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME);
    const ohPackageJson5Obj = parseJsonFile(ohPackagePath, false);
    const ohPackageModelVersion = ohPackageJson5Obj.modelVersion;

    if (!modelVersion || !ohPackageModelVersion) {
      this._log.printErrorExit('PROJECT_STRUCTURE_AND_CONFIGURATION');
      return;
    }

    if (typeof modelVersion !== 'string') {
      this._log.printErrorExit('INCOMPATIBLE_TYPES_OF_HVIGOR_MODEL_VERSION', [hvigorConfigPath], [[typeof modelVersion]]);
    }

    if (typeof ohPackageModelVersion !== 'string') {
      this._log.printErrorExit('INCOMPATIBLE_TYPES_OF_OH_MODEL_VERSION', [ohPackagePath], [[typeof ohPackageModelVersion]]);
    }

    if (modelVersion !== ohPackageModelVersion) {
      this._log.printErrorExit('INCONSISTENT_MODEL_VERSION', [modelVersion, ohPackageModelVersion, ohPackagePath]);
      return;
    }

    if (compareVersion(modelVersion, VersionConst.CURRENT_MODEL_VERSION) > 0 || compareVersion(modelVersion, VersionConst.MINIMUM_MODEL_VERSION) < 0) {
      this._log.printErrorExit('UNSUPPORTED_MODEL_VERSION_OF_HVIGOR', [modelVersion] , [[VersionConst.CURRENT_MODEL_VERSION]]);
    }

    if (
      compareVersion(ohPackageModelVersion, VersionConst.CURRENT_MODEL_VERSION) > 0 ||
      compareVersion(ohPackageModelVersion, VersionConst.MINIMUM_MODEL_VERSION) < 0
    ) {
      this._log.printErrorExit('UNSUPPORTED_MODEL_OH_PKG_OF_HVIGOR', [modelVersion, ohPackagePath] , [[VersionConst.CURRENT_MODEL_VERSION]]);
    }
  }

  public static integrationVersionCheck(isHarmonyOS: boolean, currentProduct: ProductBuildOpt, isAtomicService: boolean, profilePath: string) {
    if (!isHarmonyOS) {
      if (!currentProduct.compileSdkVersion) {
        this._log.printErrorExit('OPENHARMONY_WITHOUT_COMPLETIONSDKVERSION', [profilePath]);
      }
      return;
    }
    if (currentProduct.compileSdkVersion) {
      const current = parseApiVersion(currentProduct.compileSdkVersion, isHarmonyOS);
      const currentVersion = new ApiVersion(current.fullVersion);
      const support = parseApiVersion(VersionConst.SUPPORT_COMPILE_VERSION, isHarmonyOS);
      const supportVersion = new ApiVersion(support.fullVersion);
      if (!isEqualApiVersion(currentVersion, supportVersion) || (compareVersion(current.api, support.api) !== 0)) {
        this._log.printErrorExit('UNSUPPORTED_COMPILESDKVERSION', [currentProduct.compileSdkVersion, profilePath], [[VersionConst.SUPPORT_COMPILE_VERSION]]);
      }
    }
    if (currentProduct.compatibleSdkVersion) {
      // compatibleSdkVersion required
      const current = parseApiVersion(currentProduct.compatibleSdkVersion!, isHarmonyOS);
      const miniCompatibleVersion = isAtomicService ? VersionConst.ATOMIC_SERVICE_MINIMUM_COMPATIBLE_VERSION : VersionConst.MINIMUM_COMPATIBLE_VERSION;
      const support = parseApiVersion(miniCompatibleVersion, isHarmonyOS);
      const currentVersion = new ApiVersion(current.fullVersion);
      const supportVersion = new ApiVersion(support.fullVersion);
      if (compareApiVersion(currentVersion, supportVersion) < 0 || compareVersion(current.api, support.api) < 0) {
        this._log.printErrorExit('UNSUPPORTED_COMPATIBLESDKVERSION',
          [currentProduct.compileSdkVersion, profilePath], [[miniCompatibleVersion, VersionConst.SUPPORT_COMPILE_VERSION]]);
      }
    }
  }

  /**
   * 工程级build-profile.json5 API版本校验
   * app.compileSdkVersion与products下compileSdkVersion不可同时配置或同时不配置
   */
  public static apiConfigurationCheck(projectModel: ProjectModel) {
    const product: ProjectBuildProfile.ProductBuildOpt = findTargetProduct(projectModel);
    if (!product.compatibleSdkVersion) {
      this._log.printErrorExit('NO_COMPATIBLESDKVERSION', [projectModel.getProfilePath()]);
    }
    if (product.runtimeOS === CommonConst.OPEN_HARMONY || typeof product.compatibleSdkVersion === 'number') {
      if (!product.compileSdkVersion) {
        this._log.printErrorExit('NO_COMPILESDKVERSION_IN_OPENHARMONY', [projectModel.getProfilePath()]);
      }
    }
  }

  private static checkProducts(product: ProjectBuildProfile.ProductBuildOpt) {
    return !!(product.compileSdkVersion || product.compatibleSdkVersion);
  }

  /**
   * 检查API版本兼容性检查
   * stage 9 -- 9
   * HarmonyOS 9 -- 9
   * FA 8 -- 8
   * OpenHarmony 8 -- 8
   *
   * @param projectModel 项目模型
   * @param targetData target信息
   * @param moduleModel 模块模型
   */
  public static apiCompatibleCheck(projectModel: ProjectModel, targetData: ModuleTargetData, moduleModel?: ModuleModel): VersionCheckRes {
    if (moduleModel?.getApiType() === ApiType.FA && targetData.getCompatibleApiVersion() <= 7) {
      return {
        suggestedVersion: 8,
        errorHandler: () => {
          this._log.printErrorExit('FA_COMPATIBLESDKVERSION', [], [[projectModel?.getProfilePath()]]);
        },
      };
    }

    const success = { suggestedVersion: -1 };
    if (moduleModel?.getApiType() !== ApiType.STAGE && !targetData.isHarmonyOS()) {
      return success;
    }
    if (moduleModel?.getApiType() === ApiType.STAGE && targetData.getCompatibleApiVersion() <= 8) {
      return {
        suggestedVersion: 9,
        errorHandler: () => {
          this._log.printErrorExit('STAGE_COMPATIBLESDKVERSION', [],[[projectModel?.getProfilePath()]]);
        },
      };
    }

    const checkRes: [boolean, number] = this.hosApiMatrixCheck(targetData);
    if (!checkRes[0]) {
      return {
        suggestedVersion: checkRes[1],
        errorHandler: () => {
          this._log.printErrorExit('API_COMPATIBLESDKVERSION',
            [targetData.getCompileApiVersion(), checkRes[1]], [[checkRes[1], projectModel?.getProfilePath()]]);
        },
      };
    }
    return success;
  }

  /**
   * 检查HarmonyOS API版本兼容性
   *
   * @return [boolean, number] 是否兼容，以及最低的兼容版本
   * @param targetData
   */
  private static hosApiMatrixCheck(targetData: ModuleTargetData): [boolean, number] {
    if (!targetData.isHarmonyOS()) {
      return [true, targetData.getApiMeta().compatibleSdkVersion.version];
    }
    const minimumAllowedVersion = this.hosApiCompatible.get(targetData.getApiMeta().compileSdkVersion.version);

    // 当前API版本没有规则检查
    if (!minimumAllowedVersion) {
      return [true, -1];
    }
    return [targetData.getApiMeta().compatibleSdkVersion.version >= minimumAllowedVersion, minimumAllowedVersion];
  }

  /**
   * 根据schema规则校验，并只返回校验通过与否的结果
   *
   * @param moduleName 校验文件所属module
   * @param filePath 需要校验的JSON5文件路径
   * @param validator schema校验器
   * @returns boolean 校验结果
   */
  private static schemaCheckOnly(moduleName: string, filePath: string, validator: ValidateFunction): boolean {
    const json5Obj = getJson5Obj(filePath);
    return validator(json5Obj);
  }

  /**
   * 提交同步schema检查的工作
   * @param schemaCheckOption schema检查所需要的参数
   * @returns {boolean} 校验结果
   */
  static submitSchemaCheckWork(schemaCheckOption: SchemaCheckOption): boolean {
    const { moduleName, filePath, schemaPath, changeAppSchema = false, checkOnly = false, errorHandlers } = schemaCheckOption;
    const validator: ValidateFunction = ValidatorStore.addValidator(schemaPath, changeAppSchema);
    if (checkOnly) {
      return ValidateUtil.schemaCheckOnly(moduleName, filePath, validator);
    } else {
      return ValidateUtil.doSchemaCheck(moduleName, filePath, validator, errorHandlers);
    }
  }

  static schemaCheckJSObject(obj: object, filePath: string | undefined, validator: ValidateFunction) {
    const valid = validator(obj);
    const solution = 'Please check the following fields.';
    if (!valid) {
      if (!validator.errors) {
        return valid;
      }
      const errorInfos: ErrorInfo[] = [];
      validator.errors?.forEach((value) => {
        const errorInfo: ErrorInfo = {
          instancePath: value.instancePath,
          keyword: value.keyword,
          params: value.params,
          message: value.message,
        };
        if (filePath) {
          errorInfo.location = `${filePath?.replace(/\\/g, '/')}`;
        }

        errorInfos.push(errorInfo);
      });

      if (errorInfos.length !== 0) {
        let errMsg = '';
        errorInfos?.forEach((error) => {
          errMsg += util.format(error, os.EOL);
        });
        const messages = filePath ? `Schema validate failed, at file: ${filePath}` : 'Schema validate failed.';
        ValidateUtil._log.printErrorExit('SCHEMA_VALIDATE_FAILED', [messages],[[solution, os.EOL, errMsg]]);
      }
    }
    return valid;
  }

  /**
   * 根据schema规则校验JSON5的内容
   *
   * @param moduleName 校验文件所属module
   * @param filePath 需要校验的JSON5文件路径
   * @param validator schema校验器
   * @param errorHandlers 对schema校验结果进行定制化处理的方法数组
   * @returns {boolean} 校验结果
   */
  private static doSchemaCheck(moduleName: string, filePath: string, validator: ValidateFunction, errorHandlers?: ErrorHandlerFunction[]): boolean {
    const json5Obj = getJson5Obj(filePath);
    const valid = validator(json5Obj);
    const solution = 'Please check the following fields.';
    if (!valid) {
      if (!validator.errors) {
        return valid;
      }
      const jsonObjWithLocation = parseJsonFile(filePath, true);
      let errorInfos: ErrorInfo[] = [];
      validator.errors?.forEach((value) => {
        const errorInfo: ErrorInfo = {
          instancePath: value.instancePath,
          keyword: value.keyword,
          params: value.params,
          message: value.message,
          location: `${filePath.replace(/\\/g, '/')}`,
        };

        ValidateUtil.addLocationInfo(errorInfo, jsonObjWithLocation);
        errorInfos.push(errorInfo);
      });

      // 将所有的errorInfos串联地经过errorHandler的处理
      if (errorHandlers) {
        errorHandlers.forEach((errorHandler) => {
          errorInfos = errorHandler(errorInfos, json5Obj);
        });
      }

      if (errorInfos.length !== 0) {
        let errMsg = '';
        errorInfos?.forEach((error) => {
          errMsg += util.format(error, os.EOL);
        });
        const messages = `Schema validate failed, at file: ${filePath}`;
        ValidateUtil._log.printErrorExit('SCHEMA_VALIDATE_FAILED', [messages],[[solution, os.EOL, errMsg]]);
      }
    }
    return valid;
  }

  private static addLocationInfo(valueInfo: ErrorInfo, jsonObjWithLocation: any): void {
    let instanceStr = '';
    let instanceObj = jsonObjWithLocation;
    valueInfo.instancePath?.split('/').forEach((str) => {
      if (str === null || str === '') {
        // 此处不需要做操作
      } else if (parseInt(str).toString() === 'NaN') {
        instanceObj = instanceObj[str];
        if (instanceStr === '') {
          instanceStr += str;
        } else {
          instanceStr += `.${str}`;
        }
      } else {
        instanceObj = instanceObj[parseInt(str)];
        instanceStr += `[${str}]`;
      }
    });
    valueInfo.instancePath = instanceStr;
    valueInfo.location += `:${instanceObj._line}:${instanceObj._column + 1}`;
  }

  /**
   *根据SignConfig中的p7b文件调用sign-hap-tool获取bundleName
   *
   * @param sdkInfo sdkInfo
   * @param signingConfigObj signingConfigObject
   * @param pathInfo pathInfoIml
   * @param subDurationEvent subDurationEvent
   * @param useJavaDaemon 是否使用 java daemon
   * @return bundleName from p7b
   */
  public static async getBundleNameFromP7b(
    sdkInfo: SdkInfo,
    signingConfigObj: SigningConfigBuildOpt | undefined,
    pathInfo: ModulePathInfoIml,
    subDurationEvent: DurationEvent,
    useJavaDaemon = false,
  ): Promise<string | null> {
    if (signingConfigObj !== undefined) {
      const { verifyTool, signingConfigP7bFile, signConfigCheckJsonFile } = this.getVerifySignInfo(sdkInfo, signingConfigObj, pathInfo);
      const getBundleNameTaskName = 'verify signing configuration and get bundle name';
      const getBundleNameDurationEvent = subDurationEvent.createSubEvent(getBundleNameTaskName, '');
      getBundleNameDurationEvent.start();

      if (useJavaDaemon) {
        await this.executeVerifySingningConfigRequest(verifyTool, signingConfigP7bFile, signConfigCheckJsonFile);
      } else {
        await this.executeVerifySingningConfig(verifyTool, signingConfigP7bFile, signConfigCheckJsonFile);
      }

      getBundleNameDurationEvent.stop();
      getBundleNameDurationEvent.setLog(getBundleNameTaskName, MetricLogType.INFO);

      const signingConfigP7bJson = getJson5Obj(signConfigCheckJsonFile);
      const bundleNameFromP7b = signingConfigP7bJson['content']['bundle-info']['bundle-name'];
      if (fs.existsSync(signConfigCheckJsonFile)) {
        fs.removeSync(signConfigCheckJsonFile);
      }
      return bundleNameFromP7b;
    }
    return null;
  }

  private static getVerifySignInfo(sdkInfo: SdkInfo, signingConfigObj: SigningConfigBuildOpt, pathInfo: ModulePathInfoIml) {
    const verifyTool = sdkInfo.getVerifySignConfigTool();
    if (verifyTool === '' || !fs.existsSync(path.resolve(verifyTool))) {
      ValidateUtil._log.printErrorExit('NO_HAP_SIGNING_TOOL', [], [[sdkInfo.getSdkToolchainsDir()]]);
    }

    //  调用hap-sign-tool对p7b文件处理并获取bundleName
    const signingConfigP7bFile = signingConfigObj.material.profile;

    // 临时目录用于保存获取到的签名配置文件
    const intermediatesTemp = pathInfo.getIntermediatesTemp();
    const signConfigCheckJsonFile = path.resolve(intermediatesTemp, 'signConfigCheckJson.json');
    FileUtil.checkDirWithoutDelete(path.resolve(intermediatesTemp));
    FileUtil.checkFile(signConfigCheckJsonFile);
    return {
      verifyTool,
      signingConfigP7bFile,
      signConfigCheckJsonFile,
    };
  }

  public static getVerifySignCommands(sdkInfo: SdkInfo, signingConfigObj: SigningConfigBuildOpt | undefined, pathInfo: ModulePathInfoIml) {
    if (signingConfigObj !== undefined) {
      const { verifyTool, signingConfigP7bFile, signConfigCheckJsonFile } = this.getVerifySignInfo(sdkInfo, signingConfigObj, pathInfo);
      const builder = new JavaCommandBuilder();
      return builder
        .addCalledJarFile(verifyTool)
        .addJvmOption('verify-profile')
        .addJvmOption('-inFile')
        .addJvmOption(signingConfigP7bFile)
        .addJvmOption('-outFile')
        .addJvmOption(signConfigCheckJsonFile)
        .build();
    }
    return null;
  }

  private static async executeVerifySingningConfig(tool: string, inFile: string, outFile: string) {
    const builder = new JavaCommandBuilder();
    const commands: string[] = builder
      .addCalledJarFile(tool)
      .addJvmOption('verify-profile')
      .addJvmOption('-inFile')
      .addJvmOption(inFile)
      .addJvmOption('-outFile')
      .addJvmOption(outFile)
      .build();
    await new ProcessUtils().execute(commands);
  }

  private static async executeVerifySingningConfigRequest(verifyTool: string, inFile: string, outFile: string) {
    await sendVerifyProfileRequest(verifyTool, { inFile, outFile });
  }

  /**
   * 获取hap包中的bundleName
   * 1 优先获取product配置的bundleName
   * 2 若无bundleName则读取config.json/module.json里的bundleName
   *
   * @param targetProduct ProductBuildOpt
   * @param projectModel ProjectModelImpl
   * @return bundleName from hap
   */
  static getBundleNameFromHap(targetProduct: ProductBuildOpt, projectModel: ProjectModel): string {
    const appOptFromConfig: AppOpt = cloneDeep(projectOhosConfigManager.getOverrides()?.appOpt);
    if (appOptFromConfig?.bundleName) {
      return appOptFromConfig.bundleName;
    }
    const productBundleName = targetProduct.bundleName;
    if (productBundleName) {
      return productBundleName;
    }
    return projectModel.getDefaultBundleName();
  }

  /**
   * 查找是否有重名的配置
   *
   * @param {Named[]} names 继承names的对象组成的数组
   * @returns {(string | undefined)[]}
   */
  static searchDuplicateName(names: RequiredNamed[]): (string | undefined)[] {
    return findDuplicateElement(names.map((value) => value.name));
  }

  /**
   * 通过map过滤moduleName大小写同名问题
   *
   * @param names
   */
  private static checkUniqueModuleName(names: RequiredNamed[]): (string | undefined)[] {
    const moduleNameMap = new Map<string,string>();
    const moduleNameSet = new Set<string>();
    for (const moduleInfo of names) {
      const moduleName = moduleInfo.name;
      if(moduleNameMap.has(moduleName.toLowerCase())) {
        moduleNameSet.add(moduleName);
        moduleNameSet.add(moduleNameMap.get(moduleName.toLowerCase())!);
      } else {
        moduleNameMap.set(moduleName.toLowerCase(), moduleName);
      }
    }
    return Array.from(moduleNameSet);
  }

  /**
   * 校验继承names的数组是否有重名, 若有重名则报错
   *
   * @param {Named[] | undefined} names 继承named的数组
   * @param {string} nameValue 继承named的对象名称
   * @param {string} filePath 校验的object的路径
   */
  static validateDuplicatedName(names: RequiredNamed[] | undefined, nameValue: string, filePath: string): void {
    if (names === undefined) {
      return;
    }
    const duplicateName =
      nameValue === 'modules'
        ? ValidateUtil.checkUniqueModuleName(names)
        : ValidateUtil.searchDuplicateName(names);
    if (duplicateName.length === 0) {
      return;
    }
    ValidateUtil._log.printErrorExit('UNEQUAL_NAMES', [nameValue, duplicateName.join(',')], [[nameValue, filePath]]);
  }

  /**
   * 校验product数组里有没有default, 若无则报错
   *
   * @param {ProjectBuildProfile.ProductBuildOpt[] | undefined} products
   * @param {string} filePath
   */
  static validateContainsDefault(products: any[] | undefined, filePath: string): void {
    if (products && products.some((product) => product.name === 'default')) {
      return;
    }
    ValidateUtil._log.printErrorExit('NO_DEFAULT_PRODUCT', [], [[filePath]]);
  }

  /**
   * 判断deviceType中是否存在rich/lite设备
   *
   * @param devicesList deviceType[]
   * @param typeList  rich[]/lite[]
   * @return 若deviceType存在对应类型设备 true
   */
  public static isDeviceTypeExist(devicesList: string[], typeList: Set<string>): boolean {
    return devicesList.some((device) => {
      return typeList.has(device);
    });
  }

  /**
   * 判断deviceType中是否全部为lite设备
   *
   * @param devicesList deviceType[]
   * @param typeList lite[]
   * @return 若deviceType全部为lite设备 true
   */
  public static isAllLiteDeviceType(devicesList: string[], typeList: Set<string>): boolean {
    return devicesList.every((device) => {
      return typeList.has(device);
    });
  }

  /**
   * 混合设备类型场景校验
   * 1.deviceTypes中存在富设备 则不可存在任一瘦设备
   * 2.deviceTypes存在瘦设备 则该瘦设备唯一
   *
   * @param deviceTypeList
   */
  public static validateHybridDeviceTypes(deviceTypeList: string[]): void {
    const solution = 'Check whether the device type field is correctly configured.';
    if (
      ValidateUtil.isDeviceTypeExist(deviceTypeList, ValidateUtil.richDeviceTypes) &&
      ValidateUtil.isDeviceTypeExist(deviceTypeList, ValidateUtil.liteDeviceTypes)
    ) {
      ValidateUtil._log.printErrorExit('INCONSISTENT_DEVICE_TYPES');
    } else if (ValidateUtil.isAllLiteDeviceType(deviceTypeList, ValidateUtil.liteDeviceTypes) && deviceTypeList.length > 1) {
      ValidateUtil._log.printErrorExit('UNEQUAL_MINI_SYSTEM_DEVICE_TYPES');
    }
  }

  /**
   * 校验FA模型是否存在lite device
   *
   * @param deviceTypeList deviceType[]
   */
  public static validateFALiteDevice(deviceTypeList: string[]): void {
    if (
      deviceTypeList.some((device) => {
        return ValidateUtil.liteDeviceTypes.has(device);
      })
    ) {
      ValidateUtil._log.printErrorExit('FA_MINI_SYSTEM_DEVICE_TYPES');
    }
  }

  /**
   * 校验target定制资源路径是否合法
   *
   * @param {string} relativePath 资源相对路径
   * @param {string} absolutePath 资源绝对路径
   */
  static validateTargetCustomizeResources(relativePath: string, absolutePath: string): void {
    const regExp = '^./?[\\s\\S]*[^ ]$';
    if (!relativePath || relativePath.match(regExp) === null || !fs.existsSync(absolutePath)) {
      ValidateUtil._log.printErrorExit('INVALID_TARGETS_RESOURCE', [relativePath]);
    }
  }

  /**
   * 检查form_config.json中只有一个form的isDefault为true
   *
   * @param formsObj form数据数组
   * @param formPath form_config.json 路径
   */
  public static isDefaultCheck(formsObj: ModuleJson.FormObj[], formPath: string) {
    if (formsObj.length === 0) {
      return;
    }
    let formIsDefaultCount = 0;
    for (const formObjItem of formsObj) {
      const flag: unknown = formObjItem.isDefault;
      if (flag === true) {
        formIsDefaultCount = formIsDefaultCount + 1;
      }
    }
    if (formIsDefaultCount > 1) {
      ValidateUtil._log.printErrorExit('ONLY_DEFAULT_CARD', [formPath]);
    }
  }

  /**
   * 检查form_config.json中，updateEnabled字段标记值为"true”时，updateDuration字段和scheduleUpdateTime字段不可同时为空
   *
   * @param formsObj form数据数组
   * @param formPath form_config.json 路径
   */
  public static checkUpdateEnable(formsObj: ModuleJson.FormObj[], formPath: string) {
    for (const formObjItem of formsObj) {
      if (formObjItem.updateEnabled && formObjItem.scheduledUpdateTime === undefined && formObjItem.updateDuration === undefined) {
        ValidateUtil._log.printErrorExit('WHEN_UPDATEENABLED_EXISTS_UPDATEDURATION_AND_SCHEDULEUPDATETIME_ARE_NOT_EMPTY', [formPath]);
      }
    }
  }

  /**
   * 对har/hsp的本地依赖进行校验
   * @param moduleModel 模块模型
   * @param buildOpt buildOption
   * @param dependencies Dependency[]
   */
  public static validateLocalDependency(moduleModel: ModuleModel, buildOpt: BuildOpt, dependencies: Dependency[] | undefined) {
    if (!dependencies?.length) {
      return;
    }
    const harLocalDependencyCheck = buildOpt?.strictMode?.harLocalDependencyCheck;
    if (!harLocalDependencyCheck) {
      return;
    }
    const needVerifyTypes = [DependencyType.DEPENDENCY_TYPE_HAR, DependencyType.DEPENDENCY_TYPE_HSP];
    const moduleDir = moduleModel.getProjectDir();
    const isHspModule = moduleModel.isHspModule();
    dependencies.forEach((dept) => {
      // 非har/hsp不校验; devDependencies中的依赖不校验; 非本地依赖不校验; hsp依赖har不校验
      const deptType = dept.getDependencyType();
      const deptValue = dept.getDependedVersion();
      const isDevDept = dept.getDependencyEnum() === DependencyEnum.DEV_DEPENDENCIES;
      const isHarOrHsp = needVerifyTypes.includes(deptType);
      const isHspDeptHar = isHspModule && deptType === DependencyType.DEPENDENCY_TYPE_HAR;
      if (!isHarOrHsp || isDevDept || !isLocalDependency(deptValue) || isHspDeptHar) {
        this._log.debug('Exit dependency chick: ', `Dependency Type: ${deptType};`, `Dependency value: ${deptValue}`);
        return;
      }

      // 绝对路径 || 相对路径非本模块下，退出构建
      const localDepPath = this.FILE_START_EXP.exec(deptValue)?.[1] || deptValue;
      const deptPath = path.resolve(moduleDir, localDepPath);
      if (path.isAbsolute(localDepPath) || !FileUtil.isSubDir(moduleDir, deptPath)) {
        this._log.printErrorExit('THE_LOCAL_DEPENDENCY_BELOW_IN_MODULE_%S_IS_INVALID',
          [moduleModel.getName(), `"${dept.getDependencyName()}":"${deptValue}"`]);
      }
    });
  }
}

/**
 * errorInfo对象声明
 */
export type ErrorInfo = {
  instancePath: string;
  keyword: string;
  params: Record<string, any>;
  message?: string;
  location?: string;
  help?: string;
  solution?: string;
  reason?: string;
};

/*
 * moduleName 校验文件所属module
 * filePath 需要校验的JSON5文件路径
 * schemaPath schema路径
 * changeAppSchema 是否需要修改app.json5的schema中的properties.app.required
 * checkOnly 是否只进行校验
 * errorHandlers 消费errorInfos的方法，以进行错误信息的定制
 */
export type SchemaCheckOption = {
  moduleName: string;
  filePath: string;
  schemaPath: string;
  changeAppSchema?: boolean;
  checkOnly?: boolean;
  errorHandlers?: ErrorHandlerFunction[];
};

export type ErrorHandlerFunction = (errorInfos: ErrorInfo[], checkedFile?: object) => ErrorInfo[];
